﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Loki.Commands;
using Loki.Engine;
using Loki.Utils;

namespace Loki.UI
{
    /// <summary>
    /// Base class for viewmodels.
    /// </summary>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    public abstract class ViewModel<TModel> : TrackedObject, IDisposable, ICommandAware, IViewModel, IApplicationModelAware<TModel>
    {
        protected ViewModel()
        {
            Commands = new CommandManager(this);
            RegisterDisposable(Commands);

            Close = Commands.Create(true);
            Activate = Commands.Create(true);
            Desactivate = Commands.Create(true);
            Validate = Commands.Create();

            Commands.Handle(Activate, Activate_Execute);
            Commands.Handle(Desactivate, Desctivate_Execute);
            Commands.Handle(Validate, Validate_CanExecute, Validate_Execute);
        }

        #region  IsInitialized
        private static PropertyChangedEventArgs _IsInitializedChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.IsInitialized);

        private bool _IsInitialized;

        public bool IsInitialized
        {
            get
            {
                return _IsInitialized;
            }

            set
            {
                if (value != _IsInitialized)
                {
                    _IsInitialized = value;
                    NotifyChanged(_IsInitializedChangeArgs);
                }
            }
        }
        #endregion

        #region  IsActive
        private static PropertyChangedEventArgs _IsActiveChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.IsActive);

        private bool _IsActive;

        public bool IsActive
        {
            get
            {
                return _IsActive;
            }

            set
            {
                if (value != _IsActive)
                {
                    _IsActive = value;
                    NotifyChanged(_IsActiveChangeArgs);
                }
            }
        }
        #endregion

        #region  IsValid
        private static PropertyChangedEventArgs _IsValidChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.IsValid);

        private bool _IsValid;

        public bool IsValid
        {
            get
            {
                return _IsValid;
            }

            set
            {
                if (value != _IsValid)
                {
                    _IsValid = value;
                    NotifyChanged(_IsValidChangeArgs);
                }
            }
        }
        #endregion

        #region  ValidationEnabled
        private static PropertyChangedEventArgs _ValidationEnabledChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.ValidationEnabled);

        private bool _ValidationEnabled;

        public bool ValidationEnabled
        {
            get
            {
                return _ValidationEnabled;
            }

            set
            {
                if (value != _ValidationEnabled)
                {
                    _ValidationEnabled = value;
                    NotifyChanged(_ValidationEnabledChangeArgs);
                }
            }
        }
        #endregion

        #region  DisplayName
        private static PropertyChangedEventArgs _DisplayNameChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.DisplayName);

        private string _DisplayName;

        public string DisplayName
        {
            get
            {
                return _DisplayName;
            }

            set
            {
                if (value != _DisplayName)
                {
                    _DisplayName = value;
                    NotifyChanged(_DisplayNameChangeArgs);
                }
            }
        }
        #endregion

        #region FullDisplayName
        public virtual string FullDisplayName
        {
            get
            {
                return DisplayName;
            }
        }
        #endregion

        public bool CancelEditConfirm(CommandEventArgs e)
        {
            if (IsDirty)
            {
                ModalViewModel = ConfirmViewModel.Confirm(Messages.VMMessages.CONFIRM_EDIT_LOSS);
                bool L_Return = ((ConfirmViewModel)ModalViewModel).DialogResult;
                ModalViewModel = null;
                return L_Return;
            }

            return true;
        }

        #region Modal child
        public IViewModel ModalViewModel
        {
            get
            {
                return _ModalViewModel;
            }

            set
            {
                if (value != _ModalViewModel)
                {
                    if (value == null)
                    {
                        Activate.Execute(null);
                    }
                    else
                    {
                        Desactivate.Execute(null);
                    }

                    _ModalViewModel = value;
                    NotifyChanged(_ModalViewModelChangeArgs);
                }
            }
        }

        private static PropertyChangedEventArgs _ModalViewModelChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.ModalViewModel);

        private IViewModel _ModalViewModel;
        #endregion

        #region Commands
        protected CommandManager Commands { get; private set; }

        /// <summary>
        /// Gets the close command.
        /// </summary>
        public ICommand Close { get; private set; }

        /// <summary>
        /// Gets the activate command.
        /// </summary>
        public ICommand Activate { get; private set; }

        /// <summary>
        /// Gets the desactivate command.
        /// </summary>
        public ICommand Desactivate { get; private set; }

        /// <summary>
        /// Gets the validate command.
        /// </summary>
        public ICommand Validate { get; private set; }
        #endregion

        #region Message handling

        protected void UserMessage(string P_Message, params object[] P_Params)
        {
            ApplicationCommands.MESSAGE.Execute(string.Format(CultureInfo.InvariantCulture, P_Message, P_Params));
        }

        protected void UserWarning(string P_Warning, params object[] P_Params)
        {
            ApplicationCommands.WARNING.Execute(string.Format(CultureInfo.InvariantCulture, P_Warning, P_Params));
        }

        protected void HandleException(Exception P_Ex)
        {
            ApplicationCommands.ERROR.Execute(P_Ex);
        }

        protected void HandleError(string P_Error, params object[] P_Params)
        {
            ApplicationCommands.ERROR.Execute(string.Format(CultureInfo.InvariantCulture, P_Error, P_Params));
        }
        #endregion

        #region Background workers

        protected Action<TArgument> CreateWorker<TArgument, TResult>(string P_Title, Func<TArgument, TResult> P_DoWork, Action<TResult> P_OnComplete)
        {
            return CreateWorker<TArgument, TResult>(P_Title, P_DoWork, P_OnComplete, HandleException);
        }

        protected Action<TArgument> CreateWorker<TArgument, TResult>(string P_Title, Func<TArgument, TResult> P_DoWork, Action<TResult> P_OnComplete, Action<Exception> P_OnError)
        {
            string L_TaskDisplayTitle = string.Format(CultureInfo.InvariantCulture, "{0} : {1}", FullDisplayName, P_Title);
            ITask<TArgument> L_Worker = LokiServices.Task.CreateWorker<TArgument, TResult>(L_TaskDisplayTitle, (a) => { BeginBackgroudWork(P_Title); return P_DoWork(a); }, (r) => { EndBackgroudWork(P_Title); P_OnComplete(r); }, (e) => { EndBackgroudWork(P_Title); P_OnError(e); });

            // insert for disposing
            RegisterDisposable(L_Worker);

            return L_Worker.Run;
        }

        protected Action CreateWorker(string P_Title, Action P_DoWork, Action P_OnComplete)
        {
            return CreateWorker(P_Title, P_DoWork, P_OnComplete, HandleException);
        }

        protected Action CreateWorker(string P_Title, Action P_DoWork, Action P_OnComplete, Action<Exception> P_OnError)
        {
            string L_TaskDisplayTitle = string.Format(CultureInfo.InvariantCulture, "{0} : {1}", FullDisplayName, P_Title);
            ITask L_Worker = LokiServices.Task.CreateWorker(L_TaskDisplayTitle, () => { BeginBackgroudWork(P_Title); P_DoWork(); }, () => { EndBackgroudWork(P_Title); P_OnComplete(); }, (e) => { EndBackgroudWork(P_Title); P_OnError(e); });

            // insert for disposing
            RegisterDisposable(L_Worker);

            return L_Worker.Run;
        }

        #region  Status
        private static PropertyChangedEventArgs _StatusChangeArgs = ObservableHelper.CreateArgs<ViewModel<TModel>>(x => x.Status);

        private string _Status;

        public string Status
        {
            get
            {
                return _Status;
            }

            set
            {
                if (value != _Status)
                {
                    _Status = value;
                    NotifyChanged(_StatusChangeArgs);
                }
            }
        }
        #endregion

        private HashSet<string> _RunningProcess = new HashSet<string>();

        private void BeginBackgroudWork(string P_Title)
        {
            Status = P_Title;
            _RunningProcess.Add(P_Title);
            ApplicationCommands.UPDATESTATUS.Execute(Status);
        }

        private void EndBackgroudWork(string P_Title)
        {
            if (_RunningProcess.Contains(P_Title))
            {
                _RunningProcess.Remove(P_Title);
            }

            if (_RunningProcess.Count > 0)
            {
                Status = _RunningProcess.First();
            }
            else
            {
                Status = string.Empty;
            }

            ApplicationCommands.UPDATESTATUS.Execute(Status);
        }

        #endregion

        #region Dispose
        /// <summary>
        /// Releases all resources used by an instance of the <see cref="ViewModel" /> class.
        /// </summary>
        /// <remarks>
        /// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
        /// finalization of the instance.
        /// </remarks>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~ViewModel()
        {
            Dispose(false);
        }

        private struct DisposableSet
        {
            public IDisposable Item;
            public Action<IDisposable> Unbinder;
        }

        private List<DisposableSet> _Disposables = new List<DisposableSet>();

        protected void RegisterDisposable(IDisposable P_Disposable)
        {
            RegisterDisposable(P_Disposable, null);
        }

        protected void RegisterDisposable(IDisposable P_Disposable, Action<IDisposable> P_Unbind)
        {
            _Disposables.Add(new DisposableSet()
            {
                Item = P_Disposable,
                Unbinder = P_Unbind
            });
        }

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="ViewModel" /> class and optionally releases the managed resources.
        /// </summary>
        /// <param name="P_Disposing">Set <strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool P_Disposing)
        {
            if (P_Disposing)
            {
                foreach (var L_Item in _Disposables)
                {
                    if (L_Item.Unbinder != null)
                    {
                        L_Item.Unbinder(L_Item.Item);
                    }

                    L_Item.Item.Dispose();
                }

                _Disposables.Clear();

                // dispose change notifications
                LokiServices.Event.UnregisterPropertyChangedSource(this);
                LokiServices.Event.UnregisterStateChangedSource(this);
                LokiServices.Event.UnregisterChangedSource(this);
            }
        }
        #endregion

        #region Loading Process
        private object _InitializeLock = new object();

        public void Initialize()
        {
            lock (_InitializeLock)
            {
                if (!IsInitialized)
                {
                    BeginInit();
                    try
                    {
                        OnInit(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    EndInit(false);
                    IsInitialized = true;
                }
            }
        }

        public bool Loaded
        {
            get;
            private set;
        }

        private object _LoadLock = new object();

        public void LoadData()
        {
            lock (_LoadLock)
            {
                if (!Loaded)
                {
                    BeginInit();
                    try
                    {
                        OnLoad(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    EndInit();
                    Loaded = true;
                }
            }
        }

        public bool Started
        {
            get;
            private set;
        }

        private object _StartLock = new object();

        public void StartInteraction()
        {
            lock (_StartLock)
            {
                if (!Started)
                {
                    try
                    {
                        ValidationEnabled = true;
                        OnStart(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    Started = true;
                }
            }
        }

        #region Events

        #region Init
        public event EventHandler Init;

        protected virtual void OnInit(EventArgs e)
        {
            EventHandler L_Handler = Init;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        #region Load
        public event EventHandler Load;

        protected virtual void OnLoad(EventArgs e)
        {
            EventHandler L_Handler = Load;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        #region Start
        public event EventHandler Start;

        protected virtual void OnStart(EventArgs e)
        {
            EventHandler L_Handler = Start;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        #endregion

        #endregion

        #region Activation
        #region Events
        #region Activated
        public event EventHandler Activated;

        protected virtual void OnActivated(EventArgs e)
        {
            EventHandler L_Handler = Activated;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        #region Desactivated
        public event EventHandler Desactivated;

        protected virtual void OnDesactivated(EventArgs e)
        {
            EventHandler L_Handler = Desactivated;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion

        #endregion

        private void Activate_Execute(object P_Sender, CommandEventArgs e)
        {
            IsActive = true;
            StartInteraction();
            if (ValidationEnabled)
            {
                Validate.Execute(ValidationMode.SilentWarning);
            }

            OnActivated(EventArgs.Empty);
        }

        private void Desctivate_Execute(object P_Sender, CommandEventArgs e)
        {
            IsActive = false;
            if (ValidationEnabled)
            {
                Validate.Execute(ValidationMode.SilentWarning);
            }

            OnDesactivated(EventArgs.Empty);
        }

        #endregion

        #region Validation
        private void Validate_CanExecute(object P_Sender, CanExecuteCommandEventArgs e)
        {
            e.CanExecute |= ValidationEnabled;
        }

        private void Validate_Execute(object P_Sender, CommandEventArgs e)
        {
            ValidationMode L_Mode = e.Parameter == null ? ValidationMode.None : (ValidationMode)e.Parameter;
            IsValid &= ValidateData(L_Mode);
        }

        protected void RequiresValidation(ValidationMode P_Mode = ValidationMode.Error)
        {
            IsValid = true;
            Validate.Execute(P_Mode);
        }

        protected virtual bool ValidateData(ValidationMode P_Mode)
        {
            return P_Mode == ValidationMode.None;
        }
        #endregion

        #region Application model

        public TModel ApplicationModel
        {
            get { return ContextHelper.GetApplicationModel<TModel>(); }
        }

        #endregion

        #region Sub collections

        protected override LokiCollection<T> CreateTrackedCollection<T>(Func<T> P_Adder)
        {
            LokiCollection<T> L_Collection = base.CreateTrackedCollection<T>(P_Adder);

            RegisterDisposable(
                L_Collection,
                delegate(IDisposable d)
                {
                    L_Collection.ItemChanged -= SubCollection_ItemChanged;
                    L_Collection.CollectionChanged -= SubCollection_CollectionChanged;
                });

            return L_Collection;
        }

        protected override LokiCollection<T> CreateCollection<T>(Func<T> P_Adder)
        {
            LokiCollection<T> L_Collection = base.CreateCollection<T>(P_Adder);

            RegisterDisposable(L_Collection);

            return L_Collection;
        }
        #endregion
    }
}
